package com.auto.printer.common.annoation;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author jie
 */
@Aspect
@Component
public class RedisLockAspect {

    @Value("${spring.application.name}")
    private String applicationName;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);

    @Pointcut("@annotation(com.auto.printer.common.annoation.RedisBusinesLock)")
    public void init() {
    }


    @Around("init()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        RedisBusinesLock redisBusinesLock = null;
        String lockKey = "";
        String busines = "";

        Object[] args = proceedingJoinPoint.getArgs();
        String methodName = proceedingJoinPoint.getSignature().getName();
        Class<?> classTarget = proceedingJoinPoint.getTarget().getClass();
        Class<?>[] par = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes();
        Method objMethod = classTarget.getMethod(methodName, par);

        redisBusinesLock = objMethod.getAnnotation(RedisBusinesLock.class);
        String msg = redisBusinesLock.msg();

        String key = "";

        if (StrUtil.isBlank(redisBusinesLock.key())) {
            key = methodName;
        } else {
            key = parseKey(redisBusinesLock.key(), objMethod, args);
        }

        if (StrUtil.isBlank(redisBusinesLock.busines())) {
            busines = methodName;
        } else {
            busines = redisBusinesLock.busines();
        }

        if (StrUtil.isBlankIfStr(key)) {
            logger.error("系统参数异常:" + redisBusinesLock.key());
            throw new Exception("系统参数params异常:" + redisBusinesLock.key());
        }
        String prefix = redisBusinesLock.prefix();

        lockKey = applicationName+":"+prefix + ":" + busines + ":" + key;

        if (redisBusinesLock.showLog()) {
            logger.info("redis lock service start key:[{}]", lockKey);
        }
        Boolean aBoolean = false;

        long l = redisBusinesLock.waitTime();
        do {
            aBoolean = redisTemplate.opsForValue()
                    .setIfAbsent(lockKey, lockKey, redisBusinesLock.leaseTime(), TimeUnit.SECONDS);
            if (aBoolean) {
                break;
            }
            if (l > 0) {
                logger.info("/*******等待{}s*******/", l);
                Thread.sleep(1000);
            }
            l--;
        }
        while (l > 0);
        if (!aBoolean) {
            logger.info(msg + lockKey);
            throw new Exception(msg + lockKey);
        }
        try {
            return proceedingJoinPoint.proceed();
        } finally {
            if (!StrUtil.isBlankIfStr(lockKey)) {
                redisTemplate.delete(lockKey);
            }
        }

    }

    private String parseKey(String key, Method method, Object[] args) throws Exception {
        if (key == null || "".equals(key)) {
            return null;
        }
        if (!key.startsWith("#")) {
            return key;
        }
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);

        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中

        String keyJsonStr = key;
        List<String> keysArray;
        try {
            if (keyJsonStr.startsWith("#")) {
                keyJsonStr = keyJsonStr.substring(1, keyJsonStr.length());
            }
            keysArray = new ArrayList<>(Arrays.asList(keyJsonStr.split("\\.")));
            if (CollUtil.isEmpty(keysArray)) {
                throw new Exception("参数为空");
            }
        } catch (Exception ex) {
            logger.error("参数params格式type不正确", ex);
            ex.printStackTrace();
            throw new Exception("参数params格式type不正确");
        }
        String setParamName = keysArray.get(0);
        int valIdx = 0;
        for (int i = 0; i < paraNameArr.length; i++) {
            if (paraNameArr[i].equals(setParamName)) {
                valIdx = i;
            }
        }
        Object val = args[valIdx];

        String resultStr = null;

        if (keysArray.size() < 2) {
            context.setVariable(setParamName, val);
            return parser.parseExpression(key).getValue(context, String.class);
        }
        try {
            JSONObject setObjJson = null;
            JSONObject objJson = JSONUtil.parseObj(val);
            for (int g = 1; g < keysArray.size() - 1; g++) {
                if (setObjJson == null) {
                    setObjJson = JSONUtil.parseObj(objJson.get(keysArray.get(g)));

                } else {
                    setObjJson = setObjJson.getJSONObject(keysArray.get(g));
                }
            }
            if (setObjJson != null) {

                resultStr = setObjJson.getStr(keysArray.get(keysArray.size() - 1));
            } else {
                resultStr = objJson.getStr(keysArray.get(keysArray.size() - 1));
            }
        } catch (Exception ex) {
            context.setVariable(setParamName, val);
            resultStr = parser.parseExpression(key).getValue(context, String.class);
        }
        return resultStr;
    }


}
